In the architectural design of distributed systems, APIs are not just conduits for system interaction; they are contracts connecting different tech stacks, organizational cultures, and even development eras. Within the design details of RESTful APIs, one seemingly minor topic sparks endless debate: Should JSON field names use camelCase or snake_case?
This is not merely an aesthetic choice. It touches upon the "impedance mismatch" between backend persistence layers and frontend presentation layers, involving serialization performance, network transmission efficiency, Developer Experience (DX), and cognitive psychology.
Based on the history of programming languages, underlying technical implementation mechanisms, and the architectural decisions of industry giants like Google and Stripe, this article provides an expert-level decision guide.
1. Historical Origins: A Semiotic Choice
To understand this debate, we must trace the evolution of computer languages. Naming conventions did not appear out of thin air; they are products of hardware limitations and community cultures of specific eras.
The Origin of snake_case: C and the Unix Philosophy
The popularity of snake_case (e.g., user_id) dates back to C and Unix in the 1970s. Although early keyboards (like the Teletype Model 33) had shift keys, many early compilers were case-insensitive. To distinguish words clearly on low-resolution displays, programmers introduced the underscore to simulate spaces in natural language. This habit became deeply rooted in SQL database standards. To this day, the default column naming style for PostgreSQL and MySQL remains snake_case, laying the groundwork for the future mapping friction between APIs and databases.
The Rise of camelCase: The Hegemony of Java and JavaScript
camelCase (e.g., userId) rose with Object-Oriented Programming (Smalltalk, C++, Java). Java established the industrial standard of "PascalCase for classes, camelCase for methods/variables." The decisive turning point was the birth of JavaScript. Although JSON originated from JS object literals, the JS standard library (e.g., getElementById) adopted camelCase across the board. As AJAX and JSON replaced XML as the dominant data exchange formats, camelCase gained "native" status in the Web domain.
2. Core Conflict: Impedance Mismatch of Tech Stacks
When data flows between different languages, it inevitably encounters "impedance mismatch."
The Backend Perspective (Python/Ruby/SQL)
In the backend, Python (PEP 8) and Ruby communities strongly recommend snake_case.
- Python/Django/FastAPI: Your Pydantic models usually correspond directly to database table structures:
class UserProfile(BaseModel):
first_name: str # Python convention
last_name: strIf the API mandates camelCase, you must configure aliases or converters in the serialization layer. While feasible, this adds a layer of mapping logic.
- SQL Databases: Most database column names use
snake_case. If the API usescamelCase, the ORM layer must consistently handle the conversion. - Stripe's Choice: The reason Stripe and GitHub firmly chose
snake_caseis largely because their early architectures were built on the Ruby stack. Exposing internal models directly ensured backend consistency.
The Frontend Perspective (JavaScript/TypeScript)
In the browser, camelCase is the absolute ruler.
- Code Style Fragmentation: If an API returns
snake_case, frontend code becomes inconsistent:
const user = await fetchUser();
console.log(user.first_name); // Violates ESLint camelcase rule
render(user.email_address);ESLint will flag this as a warning, forcing developers to disable the rule or convert data immediately upon receipt.
- Destructuring Pain: In modern JS/TS development, object destructuring is ubiquitous. If fields are
snake_case, they must be renamed during destructuring to avoid polluting the local scope:
// Verbose renaming
const { first_name: firstName, last_name: lastName } = response.data;This increases boilerplate code and the likelihood of errors.
3. Performance Myths: Serialization and Network Transmission
Regarding performance, two common myths exist: "Field name conversion is too slow" and "Underscores increase payload size." Let's clarify with data.
Myth 1: Runtime Conversion Overhead
- Dynamic Languages: In Python or Ruby, converting field names via regex replacement on every request does consume CPU. However, modern frameworks (like Pydantic v2, rewritten in Rust) minimize this overhead through pre-computed Schema mappings.
- Static Languages (Go/Java/Rust): This is effectively "zero cost." Go's struct tags (
json:"firstName") or Java Jackson's mapping caches are determined at compile or startup time. Runtime execution involves simple byte copying, not dynamic calculation.
Note: Never perform global recursive conversion in the frontend (browser main thread) using interceptors (e.g., Axios). For large responses, this causes page jank and memory churn. Conclusion: The backend should handle the conversion.
Myth 2: Transmission Size and Compression
Theoretically, first_name is one byte longer than firstName. However, with Gzip or Brotli compression enabled (standard HTTP config), this difference virtually disappears.
- Principle: Compression algorithms rely on "duplicate string referencing." In a JSON array, keys are highly repetitive. The algorithm stores
first_namein a dictionary upon first encounter and replaces subsequent occurrences with a tiny pointer. - Benchmarks: Tests show that under Gzip level 6, the size difference between the two styles is typically between 0.5% and 1%. Unless you are transmitting data over satellite links, this is a non-issue.
4. Developer Experience (DX) and Cognitive Psychology
Architecture is not just about machines; it's about people.
- Readability of snake_case: Cognitive psychology suggests that underscores provide clear visual separation.
parse_db_xmlis parsed by the brain faster thanparseDbXml, especially when dealing with consecutive acronyms (e.g.,XMLHTTPRequestvsXmlHttpRequest). This is one reason Stripe's documentation is considered highly readable. - Consistency of camelCase: The flow state brought by full-stack consistency is more critical. For full-stack JS/TS teams, using
camelCaseacross the frontend and backend allows for direct reuse of type definitions (Interface/Zod Schema), completely eliminating the cognitive load of "mental translation".
5. Industry Standards and Rationale
| Organization | Choice | Core Logic & Background |
|---|---|---|
| camelCase | Google API Guide (AIP-140) mandates lowerCamelCase for JSON. Even if internal Protobuf definitions use snake_case, the external conversion layer automatically switches to camelCase to align with the Web ecosystem. |
|
| Microsoft | camelCase | With.NET Core embracing open source and the invention of TypeScript, Microsoft fully pivoted to Web standards, abandoning the early PascalCase. |
| Stripe | snake_case | A typical Ruby-stack company. They mask the difference by providing extremely robust Client SDKs. When you use the Node SDK, although snake_case is transmitted, the SDK method signatures usually follow JS conventions. |
| JSON:API | camelCase | The community-driven specification explicitly recommends camelCase, reflecting the consensus of the Web community. |
6. Deep Architectural Advice: Decoupling and DTOs
A common anti-pattern is "Pass-through": directly serializing database entities to return them.
- If you do this, and your DB columns are
snake_case, your API becomessnake_case. - Risk: This violates the principle of information hiding, exposes table structures, and creates strong coupling between the API and DB. If the DB is renamed, the API breaks.
Best Practice: Introduce a DTO (Data Transfer Object) layer. Regardless of how the underlying database is named, you should define an independent API contract (DTO). Since you are defining a DTO, why not define it as camelCase to adhere to Web standards? Modern mapping tools (MapStruct, AutoMapper, Pydantic) handle this conversion effortlessly.
7. Looking Forward: GraphQL and gRPC
GraphQL: The community almost 100% embraces camelCase. If your team plans to introduce GraphQL in the future, designing REST APIs with camelCase now is a wise "forward compatibility" strategy.
gRPC: The Protobuf standard dictates: .proto files use snake_case for field definitions, but they must be converted to camelCase when mapped to JSON. This is Google's standard solution for multi-language environments.
8. Summary and Decision Matrix
There is no absolute right or wrong, only trade-offs. Here is the final decision framework:
Recommended: Default to camelCase
For the vast majority of new, general-purpose RESTful APIs facing Web/App clients, camelCase is strongly recommended.
Reason: Align with the dominance of JSON/JavaScript/TypeScript, embracing the habits of 90% of consumers.
Tooling: Get the best support from OpenAPI code generators, Swagger UI, and modern IDEs.
When to use snake_case?
1. Specific Consumers: The primary users of the API are Python data scientists or system ops (Curl/Bash).
2. Legacy Systems: Existing APIs are already snake_case. Consistency > Best Practice. Do not mix styles within the same system.
3. Backend Velocity Extremism: Using Python/Ruby without the resources to maintain a DTO layer, directly passing through database models.
Decision Table
| Dimension | Recommended Style |
|---|---|
| Web Frontend / Mobile App | camelCase (Zero impedance, type safety) |
| Data Analysis / Scientific Computing | snake_case (Fits Python/R habits) |
| Node.js / Go / Java Backend | camelCase (Native or perfect library support) |
| Python / Ruby Backend | camelCase (Converter recommended) or snake_case (Internal tools only) |
| Full-Stack Team | The higher the full-stack degree, the more camelCase is recommended |
The essence of API design is empathy. In the context of Web APIs, encapsulating complexity in the backend (handling mapping) and leaving convenience to the user (adhering to JS habits) is the choice that reflects greater professionalism.



