From 2033f28d7ce28999f97cf5e909698afbfa9b3dcd Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 6 Oct 2025 07:59:09 +0200 Subject: [PATCH 1/4] docs: Add blog post on integrating curl-to-json-schema with massimo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add comprehensive blog post showing how to combine curl-to-json-schema with massimo and massimo-cli to create a complete workflow from curl commands to type-safe API clients. The blog covers: - Three-step pipeline (curl → schema → type-safe client) - Detailed usage examples for each tool - Four real-world workflows - Advanced configurations and best practices - Before/after comparisons - Troubleshooting guide Also adds massimo and massimo-cli as dev dependencies for examples. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- BLOG.md | 810 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 + 2 files changed, 812 insertions(+) create mode 100644 BLOG.md diff --git a/BLOG.md b/BLOG.md new file mode 100644 index 0000000..c4d2648 --- /dev/null +++ b/BLOG.md @@ -0,0 +1,810 @@ +# From curl Commands to Type-Safe API Clients: A Complete Workflow + +Have you ever reverse-engineered an API by copying curl commands from your browser's Network tab? Or documented your API with curl examples? What if you could turn those curl commands into production-ready, type-safe HTTP clients automatically? + +This post shows you how to combine three powerful tools to create a seamless workflow from curl commands to fully-typed API clients: + +1. **curl-to-json-schema** - Generate OpenAPI schemas from curl commands +2. **massimo-cli** - Generate type-safe clients from OpenAPI schemas +3. **massimo** - Runtime library for making type-safe API calls + +## The Problem: APIs Without Types + +You're working with an API that either: +- Has no documentation +- Has outdated documentation +- Has documentation but no official SDK +- Is documented only with curl examples + +Traditional approach: manually write HTTP client code, hope you got the types right, and discover errors at runtime. + +**Better approach:** Automate schema generation and client creation from real curl commands. + +## The Solution: A Three-Step Pipeline + +```bash +# Step 1: Curl commands → OpenAPI schema +curl-to-json-schema curls.txt -o api-schema.json + +# Step 2: OpenAPI schema → Type-safe client +massimo api-schema.json -n myApi --full + +# Step 3: Use the generated client +node -e "import('./myApi/myApi.mjs').then(async gen => { + const client = await gen.default({ url: 'https://api.example.com' }) + const data = await client.getUsers() + console.log(data) +})" +``` + +Let's explore each step in detail. + +## Step 1: Generate OpenAPI Schema from curl Commands + +### Installation + +```bash +npm install curl-to-json-schema +``` + +### Basic Usage + +Create a file with your curl commands (one per line): + +**curls.txt:** +```bash +# User management endpoints +curl https://api.example.com/users +curl -X POST -H "Content-Type: application/json" -d '{"name":"John","email":"john@example.com"}' https://api.example.com/users +curl -X PUT -d '{"name":"Jane","email":"jane@example.com","age":25}' https://api.example.com/users/123 + +# Product endpoints +curl https://api.example.com/products +curl -X POST -d '{"name":"Widget","price":29.99,"inStock":true}' https://api.example.com/products +``` + +Generate the schema: + +```bash +curl-to-json-schema curls.txt -o api-schema.json +``` + +**api-schema.json** (generated): +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "method": { + "type": "string", + "enum": ["GET", "POST", "PUT"] + }, + "url": { + "type": "string", + "format": "uri" + }, + "headers": { + "type": "object", + "properties": { + "Content-Type": { + "type": "string", + "enum": ["application/json"] + } + } + }, + "body": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "age": { "type": "integer" }, + "price": { "type": "number" }, + "inStock": { "type": "boolean" } + } + } + }, + "required": ["method", "url"] +} +``` + +### Progressive Schema Building + +You can build schemas incrementally as you discover new endpoints: + +```javascript +import { CurlToJsonSchema } from 'curl-to-json-schema' + +const converter = new CurlToJsonSchema() + +// Add commands one at a time +converter.convert('curl https://api.example.com/users') +converter.convert('curl -X POST -d \'{"name":"Alice"}\' https://api.example.com/users') +converter.convert('curl -X DELETE https://api.example.com/users/123') + +// Get the accumulated schema +const schema = converter.toSchema() +console.log(JSON.stringify(schema, null, 2)) +``` + +### Extending Existing Schemas + +Already have a partial schema? Extend it: + +```bash +# Merge new curl commands with existing schema +curl-to-json-schema new-endpoints.txt \ + -s existing-schema.json \ + -o merged-schema.json +``` + +Or programmatically: + +```javascript +import { CurlToJsonSchema } from 'curl-to-json-schema' +import fs from 'fs' + +const existingSchema = JSON.parse(fs.readFileSync('existing-schema.json')) +const converter = new CurlToJsonSchema({ schema: existingSchema }) + +// New curl commands will merge with existing schema +converter.convert('curl -X PATCH -d \'{"status":"active"}\' https://api.example.com/users/123') + +const mergedSchema = converter.toSchema() +``` + +### Key Features + +- **Smart Type Inference**: Automatically detects strings, numbers, booleans, arrays, objects +- **Format Detection**: Recognizes emails, URLs, UUIDs, dates +- **Schema Merging**: Intelligently combines multiple curl commands into one schema +- **Required Fields**: Marks authentication headers and critical fields as required + +## Step 2: Generate Type-Safe Client with massimo-cli + +### Installation + +```bash +npm install --save-dev massimo-cli massimo +``` + +### Basic Client Generation + +```bash +# Generate from local schema file +massimo ./api-schema.json -n apiClient + +# Generate from live OpenAPI URL +massimo http://localhost:3000/documentation/json -n apiClient + +# Generate from GraphQL endpoint +massimo http://localhost:4000/graphql -n gqlClient +``` + +This creates a folder structure: + +``` +apiClient/ +├── package.json # Module configuration +├── apiClient.d.ts # TypeScript type definitions +├── apiClient.mjs # Client implementation (ESM) +└── apiClient.openapi.json # Copy of the schema +``` + +### Client Generation Options + +**Full Request/Response Mode:** +```bash +massimo api-schema.json -n api --full +``` + +Wraps all parameters in structured objects: + +```javascript +// Without --full +await client.createUser({ name: 'John', email: 'john@example.com' }) + +// With --full +await client.createUser({ + body: { name: 'John', email: 'john@example.com' }, + headers: { 'Authorization': 'Bearer token' }, + query: { validate: true } +}) +``` + +**Frontend Client (Browser):** +```bash +massimo api-schema.json \ + --frontend \ + --language ts \ + --name browserApi \ + --with-credentials +``` + +Generates a browser-compatible client using `fetch` instead of Node.js HTTP. + +**Response Validation:** +```bash +massimo api-schema.json -n api --validate-response +``` + +Validates all API responses against the schema at runtime. + +**TypeScript Implementation:** +```bash +massimo api-schema.json -n api --typescript +``` + +Generates `.ts` files instead of `.mjs`, giving you full TypeScript source code. + +**Types Only:** +```bash +massimo api-schema.json -n apiTypes --types-only +``` + +Just the type definitions, no implementation - useful if you want to write your own client. + +### Using the Generated Client + +```javascript +import generateApiClient from './apiClient/apiClient.mjs' + +// Initialize the client +const client = await generateApiClient({ + url: 'https://api.example.com', + headers: { 'Authorization': 'Bearer your-token' }, + throwOnError: true, // Throw on HTTP errors + validateResponse: true, // Validate responses + bodyTimeout: 30000, // Request timeout + + // Dynamic headers per request + getHeaders: async (options) => { + return { 'X-Request-ID': crypto.randomUUID() } + } +}) + +// Make API calls (fully typed!) +const users = await client.getUsers({ query: { page: 1, limit: 10 } }) +const newUser = await client.createUser({ + body: { name: 'John', email: 'john@example.com' } +}) +``` + +### TypeScript Support + +```typescript +import generateApiClient from './apiClient/apiClient.mjs' +import type { ApiClient, User } from './apiClient/apiClient.d.ts' + +const client: ApiClient = await generateApiClient({ + url: 'https://api.example.com' +}) + +// All operations are fully typed +const users: User[] = await client.getUsers() +const newUser: User = await client.createUser({ + body: { name: 'Jane', email: 'jane@example.com' } +}) +``` + +## Step 3: Use massimo for Advanced Scenarios + +While `massimo-cli` generates standalone clients, you can use `massimo` directly for more dynamic scenarios. + +### Direct API Client Building + +```javascript +import { buildOpenAPIClient } from 'massimo' + +const client = await buildOpenAPIClient({ + url: 'https://api.example.com', + path: './api-schema.json', // Local schema file + fullRequest: true, + fullResponse: false, + validateResponse: true, + headers: { 'Authorization': 'Bearer token' } +}) + +const data = await client.getUsers({ query: { page: 1 } }) +``` + +### Fastify Plugin Integration + +Perfect for microservices that need to call other APIs: + +```javascript +import fastify from 'fastify' +import massimoPlugin from 'massimo/fastify-plugin' + +const app = fastify() + +// Register API client as Fastify plugin +await app.register(massimoPlugin, { + type: 'openapi', + url: 'https://external-api.com', + path: './api-schema.json', + name: 'externalApi', // Available as request.externalApi + fullRequest: true, + + // Access request context + getHeaders: async (request, reply, options) => { + return { + 'Authorization': request.headers.authorization, + 'X-Trace-ID': request.id + } + } +}) + +// Use in routes +app.get('/users', async (request, reply) => { + const users = await request.externalApi.getUsers({ + query: { limit: 10 } + }) + return users +}) + +await app.listen({ port: 3000 }) +``` + +### GraphQL Support + +Works with GraphQL too: + +```javascript +import { buildGraphQLClient } from 'massimo' + +const client = await buildGraphQLClient({ + url: 'https://api.example.com/graphql', + headers: { 'Authorization': 'Bearer token' } +}) + +const data = await client.graphql({ + query: ` + query GetUsers($limit: Int) { + users(limit: $limit) { + id + name + email + } + } + `, + variables: { limit: 10 } +}) +``` + +## Real-World Workflows + +### Workflow 1: Reverse Engineering APIs + +You're integrating with a third-party API that has no SDK: + +```bash +# 1. Inspect network traffic and save curl commands +# (Copy from browser DevTools → Network → Right click → Copy as cURL) + +# 2. Save curl commands to a file +cat > api-calls.txt << 'EOF' +curl 'https://external-api.com/users' -H 'Authorization: Bearer demo' +curl -X POST 'https://external-api.com/users' -H 'Content-Type: application/json' -d '{"name":"Test"}' +EOF + +# 3. Generate OpenAPI schema +curl-to-json-schema api-calls.txt -o external-api-schema.json + +# 4. Generate type-safe client +massimo external-api-schema.json -n externalApi --full --validate-response + +# 5. Use in your application +``` + +**app.js:** +```javascript +import generateExternalApiClient from './externalApi/externalApi.mjs' + +const client = await generateExternalApiClient({ + url: 'https://external-api.com', + headers: { 'Authorization': `Bearer ${process.env.API_TOKEN}` } +}) + +// Now you have type-safe API calls! +const users = await client.getUsers() +``` + +### Workflow 2: Documentation-Driven Development + +Your team documents APIs with curl examples: + +**docs/api-examples.txt:** +```bash +# Authentication +curl -X POST https://api.myapp.com/auth/login -d '{"username":"user","password":"pass"}' + +# User operations +curl https://api.myapp.com/users -H "Authorization: Bearer TOKEN" +curl -X POST https://api.myapp.com/users -d '{"name":"John","email":"john@example.com"}' +``` + +**CI/CD Pipeline:** +```bash +#!/bin/bash +# .github/workflows/generate-clients.yml + +# Generate schema from curl examples +curl-to-json-schema docs/api-examples.txt -o schema/openapi.json + +# Generate clients for different consumers +massimo schema/openapi.json -n nodeClient --full +massimo schema/openapi.json -n browserClient --frontend --language ts + +# Publish clients to npm +npm publish ./nodeClient +npm publish ./browserClient +``` + +Now consumers install your SDK package instead of making raw HTTP calls. + +### Workflow 3: API Testing + +Use the generated schema for testing: + +```javascript +import { CurlToJsonSchema } from 'curl-to-json-schema' +import { buildOpenAPIClient } from 'massimo' +import { test } from 'node:test' +import assert from 'node:assert' + +test('API matches documented curl examples', async () => { + // Generate expected schema from documentation + const converter = new CurlToJsonSchema() + converter.convert('curl https://api.example.com/users') + const expectedSchema = converter.toSchema() + + // Build client and test real API + const client = await buildOpenAPIClient({ + url: 'https://api.example.com', + validateResponse: true // This will throw if response doesn't match schema + }) + + // If this doesn't throw, API matches documentation + const users = await client.getUsers() + assert(Array.isArray(users)) +}) +``` + +### Workflow 4: Incremental Schema Discovery + +Building an API wrapper and discovering endpoints as you go: + +```javascript +import { CurlToJsonSchema } from 'curl-to-json-schema' +import fs from 'fs' + +class ApiSchemaBuilder { + constructor(schemaPath) { + this.schemaPath = schemaPath + this.converter = new CurlToJsonSchema() + + // Load existing schema if available + if (fs.existsSync(schemaPath)) { + const existing = JSON.parse(fs.readFileSync(schemaPath)) + this.converter = new CurlToJsonSchema({ schema: existing }) + } + } + + addCurl(curlCommand) { + this.converter.convert(curlCommand) + this.save() + } + + save() { + const schema = this.converter.toSchema() + fs.writeFileSync(this.schemaPath, JSON.stringify(schema, null, 2)) + } +} + +// Usage +const builder = new ApiSchemaBuilder('./api-schema.json') + +// Discover endpoints as you work +builder.addCurl('curl https://api.example.com/users') +builder.addCurl('curl https://api.example.com/products') +builder.addCurl('curl -X POST -d \'{"name":"New"}\' https://api.example.com/products') + +// Schema file is continuously updated +``` + +Then periodically regenerate your client: + +```bash +massimo api-schema.json -n api --full +``` + +## Advanced: Custom Client Configuration + +### Authenticated Clients + +```javascript +import generateApiClient from './api/api.mjs' + +const client = await generateApiClient({ + url: 'https://api.example.com', + + // Static headers + headers: { + 'Authorization': `Bearer ${process.env.API_TOKEN}`, + 'X-API-Version': '2024-01-01' + }, + + // Dynamic headers per request + getHeaders: async (options) => { + const token = await refreshTokenIfNeeded() + return { + 'Authorization': `Bearer ${token}`, + 'X-Request-ID': crypto.randomUUID(), + 'X-Timestamp': new Date().toISOString() + } + } +}) +``` + +### Error Handling + +```javascript +const client = await generateApiClient({ + url: 'https://api.example.com', + throwOnError: false // Don't throw, handle manually +}) + +const response = await client.getUsers() + +if (response.statusCode >= 400) { + console.error('API Error:', response.statusCode, response.body) +} else { + console.log('Success:', response.body) +} +``` + +### Timeout Configuration + +```javascript +const client = await generateApiClient({ + url: 'https://api.example.com', + bodyTimeout: 30000, // 30 second body timeout + headersTimeout: 10000, // 10 second header timeout +}) +``` + +## Comparison: Before and After + +### Before (Manual HTTP Calls) + +```javascript +import fetch from 'node-fetch' + +async function getUsers(page = 1) { + const response = await fetch( + `https://api.example.com/users?page=${page}`, + { + headers: { + 'Authorization': 'Bearer token', + 'Content-Type': 'application/json' + } + } + ) + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`) + } + + return response.json() // No type safety +} + +async function createUser(data) { + const response = await fetch( + 'https://api.example.com/users', + { + method: 'POST', + headers: { + 'Authorization': 'Bearer token', + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) // No validation + } + ) + + return response.json() +} + +// Usage - no autocomplete, no type checking +const users = await getUsers(1) +const newUser = await createUser({ name: 'John' }) // Typos possible +``` + +### After (Generated Type-Safe Client) + +```javascript +import generateApiClient from './api/api.mjs' + +const client = await generateApiClient({ + url: 'https://api.example.com', + headers: { 'Authorization': 'Bearer token' }, + validateResponse: true, + throwOnError: true +}) + +// Usage - fully typed, autocomplete, validation +const users = await client.getUsers({ query: { page: 1 } }) +const newUser = await client.createUser({ + body: { name: 'John', email: 'john@example.com' } +}) // TypeScript will catch missing/wrong fields +``` + +## Best Practices + +### 1. Keep curl Examples Updated + +```bash +# Store curl examples in version control +docs/ + api-examples/ + users.txt + products.txt + auth.txt + +# Regenerate schema in CI +curl-to-json-schema docs/api-examples/*.txt -o schema/openapi.json +``` + +### 2. Validate Real API Calls + +```javascript +const client = await generateApiClient({ + url: process.env.API_URL, + validateResponse: true, // Catch schema drift + throwOnError: true +}) +``` + +### 3. Version Your Schemas + +```bash +# Tag schema versions +cp api-schema.json schemas/v1.0.0.json +git tag api-schema-v1.0.0 +``` + +### 4. Separate Dev Dependencies + +```bash +# massimo-cli is only needed for code generation +npm install --save-dev massimo-cli curl-to-json-schema + +# massimo runtime is needed in production +npm install massimo +``` + +### 5. Automate Client Regeneration + +```json +{ + "scripts": { + "generate:schema": "curl-to-json-schema docs/api-examples.txt -o api-schema.json", + "generate:client": "massimo api-schema.json -n api --full --validate-response", + "generate": "npm run generate:schema && npm run generate:client" + } +} +``` + +## Troubleshooting + +### Issue: Generated Client Has No Methods + +**Problem:** Schema doesn't define operations/paths. + +**Solution:** curl-to-json-schema generates a JSON Schema, but massimo expects OpenAPI format. Convert manually: + +```javascript +import fs from 'fs' + +const jsonSchema = JSON.parse(fs.readFileSync('api-schema.json')) + +const openApiSchema = { + openapi: '3.0.0', + info: { title: 'API', version: '1.0.0' }, + paths: { + '/users': { + get: { + operationId: 'getUsers', + responses: { + '200': { + description: 'Success', + content: { + 'application/json': { schema: jsonSchema } + } + } + } + } + } + } +} + +fs.writeFileSync('openapi.json', JSON.stringify(openApiSchema, null, 2)) +``` + +### Issue: Type Errors in Generated Client + +**Problem:** Schema has conflicting types. + +**Solution:** Review your curl commands - you might be sending different types to the same endpoint: + +```bash +# These create conflicting schemas: +curl -d '{"age":25}' https://api.example.com/users # age: integer +curl -d '{"age":"25"}' https://api.example.com/users # age: string + +# Fix: Be consistent +curl -d '{"age":25}' https://api.example.com/users +``` + +### Issue: Authentication Fails + +**Problem:** Headers not being sent correctly. + +**Solution:** Use `getHeaders` for dynamic auth: + +```javascript +const client = await generateApiClient({ + url: 'https://api.example.com', + getHeaders: async () => { + const token = await getAuthToken() + return { 'Authorization': `Bearer ${token}` } + } +}) +``` + +## Conclusion + +The combination of **curl-to-json-schema**, **massimo-cli**, and **massimo** creates a powerful workflow: + +1. **curl-to-json-schema** transforms curl commands into structured schemas +2. **massimo-cli** generates type-safe, validated API clients from those schemas +3. **massimo** provides the runtime to make those API calls with confidence + +This pipeline is perfect for: +- Reverse engineering APIs without documentation +- Creating SDKs from API examples +- Ensuring API consumers stay in sync with your API +- Building type-safe microservice communication +- Testing APIs against their documentation + +No more manual HTTP client code. No more runtime type errors. Just capture curl commands and get production-ready, type-safe API clients. + +## Resources + +- [curl-to-json-schema](https://github.com/matteo) - Generate schemas from curl commands +- [massimo](https://github.com/platformatic/massimo) - Type-safe HTTP client library +- [massimo-cli](https://github.com/platformatic/massimo) - Client generator CLI +- [OpenAPI Specification](https://swagger.io/specification/) - API schema standard + +## Try It Now + +```bash +# Install all tools +npm install curl-to-json-schema massimo +npm install --save-dev massimo-cli + +# Create example curl commands +cat > api.txt << 'EOF' +curl https://jsonplaceholder.typicode.com/users +curl https://jsonplaceholder.typicode.com/posts +EOF + +# Generate schema +curl-to-json-schema api.txt -o schema.json + +# Generate client (note: you'll need to convert to OpenAPI format first) +massimo schema.json -n myClient + +# Start building! +``` + +Happy API client building! diff --git a/package.json b/package.json index 8a0d074..bdaf534 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ }, "devDependencies": { "eslint": "^9.34.0", + "massimo": "^1.0.1", + "massimo-cli": "^1.0.1", "neostandard": "^0.12.2" } } From b53d54c0ea5c5b999671136f82654c4ac572cdba Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 6 Oct 2025 08:01:57 +0200 Subject: [PATCH 2/4] docs: Remove GraphQL references and expand frontend client section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove GraphQL examples and mentions (out of scope) - Add detailed frontend client usage section - Highlight fetch-based browser client benefits - Add browser usage example with localStorage auth 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- BLOG.md | 59 ++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/BLOG.md b/BLOG.md index c4d2648..c162318 100644 --- a/BLOG.md +++ b/BLOG.md @@ -175,9 +175,6 @@ massimo ./api-schema.json -n apiClient # Generate from live OpenAPI URL massimo http://localhost:3000/documentation/json -n apiClient - -# Generate from GraphQL endpoint -massimo http://localhost:4000/graphql -n gqlClient ``` This creates a folder structure: @@ -220,7 +217,7 @@ massimo api-schema.json \ --with-credentials ``` -Generates a browser-compatible client using `fetch` instead of Node.js HTTP. +Generates a browser-compatible client using the native `fetch` API instead of Node.js HTTP. This is perfect for client-side applications, SPAs, and any browser-based code. The frontend client is lightweight, has no Node.js dependencies, and works in all modern browsers. **Response Validation:** ```bash @@ -345,32 +342,50 @@ app.get('/users', async (request, reply) => { await app.listen({ port: 3000 }) ``` -### GraphQL Support +### Frontend Client Usage -Works with GraphQL too: +For browser-based applications, use the `--frontend` flag to generate a fetch-based client: -```javascript -import { buildGraphQLClient } from 'massimo' +```bash +massimo api-schema.json \ + --frontend \ + --language ts \ + --name apiClient \ + --with-credentials +``` -const client = await buildGraphQLClient({ - url: 'https://api.example.com/graphql', - headers: { 'Authorization': 'Bearer token' } -}) +Then use it in your frontend code: -const data = await client.graphql({ - query: ` - query GetUsers($limit: Int) { - users(limit: $limit) { - id - name - email - } +```typescript +import generateApiClient from './apiClient/apiClient.js' + +// Works in the browser with native fetch +const client = await generateApiClient({ + url: 'https://api.example.com', + + // Optional: include credentials (cookies) + getHeaders: async () => { + return { + 'Authorization': `Bearer ${localStorage.getItem('token')}`, + 'X-Client-Version': '1.0.0' } - `, - variables: { limit: 10 } + } +}) + +// Make API calls from the browser +const users = await client.getUsers({ query: { page: 1 } }) +const newUser = await client.createUser({ + body: { name: 'Jane', email: 'jane@example.com' } }) ``` +The frontend client: +- Uses native `fetch` API (no Node.js dependencies) +- Supports CORS and credentials +- Works with all modern browsers +- Can be bundled with Vite, Webpack, or other bundlers +- Fully type-safe when using TypeScript + ## Real-World Workflows ### Workflow 1: Reverse Engineering APIs From a3cf0044bd13b4a5284970da9d92edbffeb853ad Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 6 Oct 2025 08:04:10 +0200 Subject: [PATCH 3/4] refactor: Use promise-based fs API instead of sync methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace all fs.readFileSync and fs.writeFileSync with promise-based readFile and writeFile from node:fs/promises for better async handling. Changes: - Import from 'node:fs/promises' instead of 'fs' - Add await to all file operations - Update ApiSchemaBuilder class with async init() method - Keep existsSync for synchronous existence checks 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- BLOG.md | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/BLOG.md b/BLOG.md index c162318..2435496 100644 --- a/BLOG.md +++ b/BLOG.md @@ -141,9 +141,9 @@ Or programmatically: ```javascript import { CurlToJsonSchema } from 'curl-to-json-schema' -import fs from 'fs' +import { readFile } from 'node:fs/promises' -const existingSchema = JSON.parse(fs.readFileSync('existing-schema.json')) +const existingSchema = JSON.parse(await readFile('existing-schema.json', 'utf-8')) const converter = new CurlToJsonSchema({ schema: existingSchema }) // New curl commands will merge with existing schema @@ -491,38 +491,42 @@ Building an API wrapper and discovering endpoints as you go: ```javascript import { CurlToJsonSchema } from 'curl-to-json-schema' -import fs from 'fs' +import { readFile, writeFile } from 'node:fs/promises' +import { existsSync } from 'node:fs' class ApiSchemaBuilder { constructor(schemaPath) { this.schemaPath = schemaPath this.converter = new CurlToJsonSchema() + } + async init() { // Load existing schema if available - if (fs.existsSync(schemaPath)) { - const existing = JSON.parse(fs.readFileSync(schemaPath)) + if (existsSync(this.schemaPath)) { + const existing = JSON.parse(await readFile(this.schemaPath, 'utf-8')) this.converter = new CurlToJsonSchema({ schema: existing }) } } - addCurl(curlCommand) { + async addCurl(curlCommand) { this.converter.convert(curlCommand) - this.save() + await this.save() } - save() { + async save() { const schema = this.converter.toSchema() - fs.writeFileSync(this.schemaPath, JSON.stringify(schema, null, 2)) + await writeFile(this.schemaPath, JSON.stringify(schema, null, 2)) } } // Usage const builder = new ApiSchemaBuilder('./api-schema.json') +await builder.init() // Discover endpoints as you work -builder.addCurl('curl https://api.example.com/users') -builder.addCurl('curl https://api.example.com/products') -builder.addCurl('curl -X POST -d \'{"name":"New"}\' https://api.example.com/products') +await builder.addCurl('curl https://api.example.com/users') +await builder.addCurl('curl https://api.example.com/products') +await builder.addCurl('curl -X POST -d \'{"name":"New"}\' https://api.example.com/products') // Schema file is continuously updated ``` @@ -718,9 +722,9 @@ npm install massimo **Solution:** curl-to-json-schema generates a JSON Schema, but massimo expects OpenAPI format. Convert manually: ```javascript -import fs from 'fs' +import { readFile, writeFile } from 'node:fs/promises' -const jsonSchema = JSON.parse(fs.readFileSync('api-schema.json')) +const jsonSchema = JSON.parse(await readFile('api-schema.json', 'utf-8')) const openApiSchema = { openapi: '3.0.0', @@ -742,7 +746,7 @@ const openApiSchema = { } } -fs.writeFileSync('openapi.json', JSON.stringify(openApiSchema, null, 2)) +await writeFile('openapi.json', JSON.stringify(openApiSchema, null, 2)) ``` ### Issue: Type Errors in Generated Client From 86d9a724bb6d8189435379e9325f569127987504 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 6 Oct 2025 08:05:02 +0200 Subject: [PATCH 4/4] docs: Remove Step 3 advanced scenarios section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the "Use massimo for Advanced Scenarios" section including: - Direct API Client Building - Fastify Plugin Integration Keep the Frontend Client Usage section as a standalone section. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- BLOG.md | 61 +-------------------------------------------------------- 1 file changed, 1 insertion(+), 60 deletions(-) diff --git a/BLOG.md b/BLOG.md index 2435496..83dc42b 100644 --- a/BLOG.md +++ b/BLOG.md @@ -283,66 +283,7 @@ const newUser: User = await client.createUser({ }) ``` -## Step 3: Use massimo for Advanced Scenarios - -While `massimo-cli` generates standalone clients, you can use `massimo` directly for more dynamic scenarios. - -### Direct API Client Building - -```javascript -import { buildOpenAPIClient } from 'massimo' - -const client = await buildOpenAPIClient({ - url: 'https://api.example.com', - path: './api-schema.json', // Local schema file - fullRequest: true, - fullResponse: false, - validateResponse: true, - headers: { 'Authorization': 'Bearer token' } -}) - -const data = await client.getUsers({ query: { page: 1 } }) -``` - -### Fastify Plugin Integration - -Perfect for microservices that need to call other APIs: - -```javascript -import fastify from 'fastify' -import massimoPlugin from 'massimo/fastify-plugin' - -const app = fastify() - -// Register API client as Fastify plugin -await app.register(massimoPlugin, { - type: 'openapi', - url: 'https://external-api.com', - path: './api-schema.json', - name: 'externalApi', // Available as request.externalApi - fullRequest: true, - - // Access request context - getHeaders: async (request, reply, options) => { - return { - 'Authorization': request.headers.authorization, - 'X-Trace-ID': request.id - } - } -}) - -// Use in routes -app.get('/users', async (request, reply) => { - const users = await request.externalApi.getUsers({ - query: { limit: 10 } - }) - return users -}) - -await app.listen({ port: 3000 }) -``` - -### Frontend Client Usage +## Frontend Client Usage For browser-based applications, use the `--frontend` flag to generate a fetch-based client: