Overview
API development has many approaches. This guide helps you choose the right tool for your situation by comparing firestone's resource-first approach to alternatives.
TL;DR:
- Use firestone when: You want to define data models once and generate multiple outputs (specs, CLIs, UIs)
- Use code-first when: You have existing code and want to document it
- Use OpenAPI-first when: You need maximum control over every API detail
- Use GraphQL when: Clients need flexible querying of complex data graphs
The Three Approaches
1. Resource-First (Firestone)
Philosophy: Define your data model with JSON Schema. Generate everything else from it.
Workflow:
Resource YAML β OpenAPI + AsyncAPI + CLI + Streamlit
Advantages:
- β Single source of truth - Data model drives everything
- β Multiple outputs - One definition β many artifacts
- β JSON Schema validation - Industry-standard validation
- β Less code to maintain - Automate boilerplate
- β Consistency guaranteed - All outputs match the schema
Disadvantages:
- β Opinionated structure - REST conventions enforced
- β Less control - Can't customize every OpenAPI detail
- β Learning curve - Need to learn resource schema format
When to use:
- Building new APIs from scratch
- Data-centric applications
- CRUD-heavy applications
- Microservices with standard patterns
- Generating multiple outputs (spec + CLI + docs)
2. Code-First
Philosophy: Write code first. Generate API documentation from code.
Examples:
- FastAPI (Python)
- Axum with utoipa (Rust)
- Spring Boot (Java)
- NestJS (TypeScript)
Workflow:
Python/Rust/Java code β OpenAPI spec β Client SDKs
Advantages:
- β Code is truth - Implementation and docs can't diverge
- β Type safety - Compiler enforces correctness
- β Incremental adoption - Add docs to existing code
- β IDE support - Autocomplete, refactoring work
- β Framework features - Dependency injection, middleware, etc.
Disadvantages:
- β More boilerplate - Write routes, handlers, validation
- β Single output - Just OpenAPI spec
- β Tied to language - Can't switch easily
- β Implementation details leak - Code structure affects API
When to use:
- Existing codebase you want to document
- Complex business logic in handlers
- Team already familiar with a web framework
- Need framework-specific features (auth, middleware, etc.)
3. OpenAPI-First
Philosophy: Write OpenAPI spec by hand. Generate code from it.
Tools:
- Swagger Editor
- Stoplight Studio
- OpenAPI Generator
- Redocly
Workflow:
Hand-written OpenAPI YAML β Server stubs + Client SDKs
Advantages:
- β Maximum control - Every detail of the spec
- β Language agnostic - Generate any language from spec
- β API design focus - Think about API before implementation
- β Tooling ecosystem - Mature OpenAPI tools
- β Contract-first - Spec is the contract
Disadvantages:
- β Verbose - OpenAPI specs are large and repetitive
- β Hard to maintain - Manual changes to large YAML files
- β No abstraction - Repeat yourself for similar endpoints
- β Validation gaps - Easy to make spec mistakes
- β Schema drift - Implementation can diverge from spec
When to use:
- APIs with unusual patterns OpenAPI handles well
- Need extreme control over API design
- Strong API governance requirements
- Team has OpenAPI expertise
- Generating clients in many languages
Detailed Comparison
Firestone vs FastAPI (Code-First)
FastAPI Example
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, EmailStr
from typing import Optional
app = FastAPI(title="User API")
class User(BaseModel):
name: str
email: EmailStr
age: Optional[int] = None
users_db = {}
@app.get("/users")
def list_users():
return list(users_db.values())
@app.post("/users", status_code=201)
def create_user(user: User):
user_id = str(len(users_db) + 1)
users_db[user_id] = user
return {"id": user_id, **user.dict()}
@app.get("/users/{user_id}")
def get_user(user_id: str):
if user_id not in users_db:
raise HTTPException(status_code=404, detail="User not found")
return users_db[user_id]
Firestone Equivalent
kind: users
apiVersion: v1
metadata:
description: User management API
methods:
resource: [get, post]
instance: [get]
schema:
type: array
key:
name: user_id
schema: {type: string}
items:
type: object
properties:
name: {type: string, minLength: 1}
email: {type: string, format: email}
age: {type: integer, minimum: 0}
required: [name, email]
Then generate:
# OpenAPI spec
firestone generate -r user.yaml -t "User API" openapi > spec.yaml
# CLI
firestone generate -r user.yaml -t "User API" cli --pkg user_cli --client-pkg user_client > cli.py
# Streamlit UI
firestone generate -r user.yaml -t "User API" streamlit --backend-url http://localhost:8000 > ui.py
Comparison:
| Aspect | FastAPI | Firestone |
|---|---|---|
| Lines of code | ~30 | ~15 YAML |
| Outputs | OpenAPI spec | OpenAPI + CLI + UI |
| Type safety | Python types | JSON Schema |
| Learning curve | Medium (FastAPI + Pydantic) | Medium (YAML + JSON Schema) |
| Flexibility | High (write any Python code) | Medium (REST conventions) |
| Boilerplate | More (routes + handlers) | Less (generated) |
Choose FastAPI if:
- You need custom business logic in handlers
- You want to use Python's type system
- You have an existing Python codebase
- You need FastAPI-specific features (dependency injection, background tasks)
Choose Firestone if:
- You want to generate multiple outputs (spec + CLI + UI)
- Your API follows standard CRUD patterns
- You want less boilerplate
- You prefer data-model-driven development
Firestone vs OpenAPI-First
OpenAPI-First Example
openapi: 3.0.0
info:
title: User API
version: 1.0.0
paths:
/users:
get:
summary: List users
responses:
'200':
description: Success
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
post:
summary: Create user
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UserCreate'
responses:
'201':
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/User'
/users/{user_id}:
get:
summary: Get user
parameters:
- name: user_id
in: path
required: true
schema:
type: string
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: Not found
components:
schemas:
User:
type: object
properties:
user_id: {type: string}
name: {type: string}
email: {type: string, format: email}
age: {type: integer}
required: [user_id, name, email]
UserCreate:
type: object
properties:
name: {type: string}
email: {type: string, format: email}
age: {type: integer}
required: [name, email]
Comparison:
| Aspect | OpenAPI-First | Firestone |
|---|---|---|
| Lines of YAML | ~60 | ~15 |
| Control over spec | Maximum | Medium |
| Repetition | High (DRY violations) | Low (abstracted) |
| Custom responses | Easy | Limited |
| Schema reuse | Manual $ref | Automatic |
| Maintainability | Hard (large files) | Easy (small files) |
Choose OpenAPI-First if:
- You need custom response codes (e.g., 202 Accepted, 409 Conflict)
- You want to define non-CRUD operations
- You need specific OpenAPI extensions
- You're documenting a legacy API with unusual patterns
Choose Firestone if:
- Your API follows standard CRUD patterns
- You want to avoid repetitive YAML
- You want automated schema reuse
- You prefer a higher-level abstraction
Firestone vs GraphQL
GraphQL Example
type User {
id: ID!
name: String!
email: String!
age: Int
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
}
type Query {
user(id: ID!): User
users: [User!]!
post(id: ID!): Post
}
type Mutation {
createUser(name: String!, email: String!, age: Int): User!
createPost(title: String!, content: String!, authorId: ID!): Post!
}
Comparison:
| Aspect | GraphQL | Firestone (REST) |
|---|---|---|
| Query flexibility | High (client chooses fields) | Low (fixed endpoints) |
| Over/under-fetching | Eliminated | Common |
| Caching | Complex (needs Apollo) | Simple (HTTP caching) |
| Learning curve | Steep | Gentle |
| Tooling | Specialized | Standard HTTP |
| Type system | GraphQL types | JSON Schema |
Choose GraphQL if:
- Clients need to query complex, nested data
- You want to avoid over-fetching
- You have many different client types (web, mobile, etc.)
- Your data model is a graph (with relationships)
Choose Firestone if:
- Your API is primarily CRUD operations
- You want simple HTTP semantics
- You need OpenAPI compatibility
- You prefer REST conventions
Decision Matrix
Choose Firestone When
β Building a new API from scratch β API follows CRUD patterns β Need multiple outputs (spec + CLI + UI) β Want to minimize boilerplate β Data model is the core complexity β Team comfortable with YAML and JSON Schema β Want generated, consistent specs β Need AsyncAPI support
Choose Code-First (FastAPI/Axum) When
β Have existing code to document β Complex business logic in handlers β Team is strong in a specific language β Need framework-specific features β Want type-safe compilation β Prefer code over configuration β Need fine-grained control over implementation
Choose OpenAPI-First When
β Need maximum control over API spec β API has unusual patterns β Strong API governance requirements β Generating clients in many languages β Team has OpenAPI expertise β Want spec as the contract β Non-CRUD operations dominate
Choose GraphQL When
β Complex, interconnected data model β Many different client types β Clients need flexible querying β Want to eliminate over/under-fetching β Real-time subscriptions needed β Team has GraphQL expertise
Hybrid Approaches
Firestone + FastAPI
- Use firestone to generate OpenAPI spec
- Generate FastAPI server stub from spec
- Implement business logic in FastAPI handlers
- Use generated firestone CLI for testing
Advantages:
- Firestone for boilerplate reduction
- FastAPI for custom logic
- Best of both worlds
Firestone + OpenAPI Customization
- Generate base OpenAPI spec with firestone
- Post-process with custom scripts to add:
- Custom response codes
- OpenAPI extensions
- Additional operations
- Validate with Spectral
Advantages:
- 80% automation from firestone
- 20% customization for edge cases
Migration Paths
From Code-First to Firestone
- Extract resource schemas from existing code
- Create firestone resource YAML files
- Generate OpenAPI spec
- Compare with code-generated spec
- Gradually replace endpoints
From OpenAPI-First to Firestone
- Analyze existing OpenAPI spec
- Identify CRUD patterns
- Extract schemas into firestone resources
- Generate new spec and compare
- Migrate standard operations first
From REST to GraphQL
(Firestone doesn't help here - different paradigms)
When NOT to Use Firestone
β Complex, non-CRUD operations - E.g., /search, /report, /export
β Highly customized responses - Different schemas per status code
β Legacy API with quirks - Unusual patterns that don't fit REST
β GraphQL/gRPC APIs - Firestone generates REST/AsyncAPI only
β Minimal schema complexity - Overhead not worth it for simple APIs
Complementary Tools
Firestone works well with:
- openapi-generator - Generate client SDKs from firestone's OpenAPI output
- Spectral - Validate generated OpenAPI specs
- FastAPI - Implement server logic based on firestone specs
- Docker - Containerize firestone workflows
- GitHub Actions - Automate spec generation in CI/CD
Example Scenarios
Scenario 1: Startup Building MVP
Situation: Need to build user management API quickly Recommendation: Firestone Why: Minimize boilerplate, generate spec + CLI + UI from one definition
Scenario 2: Enterprise with Existing Java Codebase
Situation: Documenting 50 existing REST endpoints Recommendation: Code-first (Spring Boot + Swagger) Why: Already have implementation, just need docs
Scenario 3: Complex E-Commerce with Recommendations
Situation: Product catalog + recommendations + real-time inventory Recommendation: Hybrid: Firestone for CRUD + Custom OpenAPI for algorithms Why: Standard operations (products, orders) fit firestone; custom endpoints handwritten
Scenario 4: Mobile App with Flexible Data Fetching
Situation: iOS + Android + Web apps need different data subsets Recommendation: GraphQL Why: Clients need query flexibility, avoid over-fetching
Scenario 5: API-as-a-Product
Situation: Public API with strict design standards Recommendation: OpenAPI-first Why: Need maximum control, API design is critical
Conclusion
Firestone is best when:
- You have well-defined data models
- Your API follows REST conventions
- You want to generate multiple outputs
- You prefer declarative configuration over imperative code
The resource-first philosophy shines when data is the API. If your API is primarily about CRUD operations on resources, firestone will save you time and ensure consistency across specs, CLIs, and documentation.
For complex business logic, unusual patterns, or existing codebases, code-first or OpenAPI-first approaches may be better fits.
The right choice depends on your project's unique constraints. There's no one-size-fits-all answerβbut understanding the trade-offs helps you make an informed decision.
See Also
- Why Resource-First? - Firestone's philosophy
- Quickstart Tutorial - Try firestone
- Examples - See firestone in action
- Best Practices - Recommended patterns