You've been there. You pull data from a third-party API, wire it into your frontend, and something breaks. After twenty minutes of debugging, you realize the API returns user_name but your React component expects userName. Or your database column is created_at, your API response is createdAt, and your UI label says "Created At" — three representations of the same concept, none of them talking to each other.
Text casing sounds trivial. It isn't. It's the kind of thing that causes subtle bugs, silently corrupts data, and makes codebases feel chaotic. Let's fix that.
What Is Case Conversion, Exactly?
Case conversion is the process of transforming a string from one naming convention to another. Different parts of a software stack often have different conventions — and those conventions exist for good reasons.
Here's a quick reference:
| Case Type | Example | Common Usage |
|---|---|---|
camelCase |
firstName |
JavaScript variables, JSON keys, Java |
PascalCase |
FirstName |
Classes, React components, C# |
snake_case |
first_name |
Python, Ruby, SQL, PostgreSQL columns |
SCREAMING_SNAKE_CASE |
FIRST_NAME |
Constants, environment variables |
kebab-case |
first-name |
HTML attributes, CSS classes, URLs |
dot.case |
first.name |
Config files, logging, some CLIs |
Title Case |
First Name |
UI labels, headings |
flatcase |
firstname |
Some legacy DBs, rare usages |
Each convention emerged from specific tooling, communities, and language constraints. Python's PEP 8 mandates snake_case for variables. JavaScript's ecosystem largely settles on camelCase. CSS thrives on kebab-case. None of them are wrong — but mixing them without intention creates friction.
Where Case Conventions Actually Matter
1. REST APIs and JSON
JSON has no formal naming convention, but camelCase is the de facto standard in JavaScript-heavy ecosystems, while snake_case is common in Python/Django and Ruby/Rails APIs.
The problem? A Python backend might return:
{
"user_id": 42,
"first_name": "Priya",
"created_at": "2024-01-15T10:00:00Z"
}
While the React frontend developer writes:
// This will silently be undefined
const name = user.firstName;
No error is thrown. The field just doesn't exist. These are the bugs that cost you an hour on a Friday afternoon.
2. Database Columns
PostgreSQL is case-insensitive by default and conventionally uses snake_case. MySQL is often case-insensitive too. But your ORM might auto-convert to camelCase for your application layer — and it might not.
-- In your migration
CREATE TABLE users (
id SERIAL PRIMARY KEY,
first_name VARCHAR(100),
created_at TIMESTAMP
);
// In Sequelize (Node ORM), underscored: true maps snake_case → camelCase
// Without it, you'd access user.first_name in JS — awkward
const user = await User.findByPk(1);
console.log(user.firstName); // Works with underscored: true
console.log(user.first_name); // Works without it — but inconsistent with JS style
Missing this configuration means your data models look inconsistent — some fields camelCase, others snake_case, decided by the order someone ran sequelize init.
3. URLs and SEO
URLs should be human-readable and SEO-friendly. The universal answer here is kebab-case.
✅ https://example.com/blog/understanding-case-conversion
✅ https://example.com/products/blue-running-shoes
❌ https://example.com/blog/understandingCaseConversion
❌ https://example.com/blog/understanding_case_conversion
Google treats hyphens as word separators and indexes them correctly. Underscores are treated as joiners — running_shoes is seen as the single word runningshoes by some crawlers. This is a real SEO pitfall.
4. Environment Variables and Config
Environment variables have a clear universal convention: SCREAMING_SNAKE_CASE.
# .env
DATABASE_URL=postgres://localhost/myapp
API_SECRET_KEY=abc123
MAX_RETRY_COUNT=3
Mixing casing here is particularly dangerous. Some systems (Docker, Kubernetes, shell scripts) are case-sensitive. api_secret_key and API_SECRET_KEY can coexist as different variables in the same environment, and mistyping one silently reads as undefined.
5. CSS Classes
BEM, utility-first (Tailwind), and CSS Modules each have their own conventions, but raw class names in CSS universally use kebab-case.
/* ✅ Conventional */
.card-header { }
.is-active { }
.has-error { }
/* ❌ Unconventional */
.cardHeader { }
.IsActive { }
CSS is case-sensitive in some contexts (SVG, XML namespaces). In HTML, class names are technically case-sensitive. .card-header and .Card-Header are different selectors.
Common Mistakes Developers Make
Assuming the other side uses your convention
This is the most common mistake. You build a form and name your fields firstName and lastName. Your backend developer names the API fields first_name and last_name. Both of you are right for your context. Neither of you talked about it. The integration silently fails.
Fix: Agree on API contract conventions upfront. Use tools like OpenAPI/Swagger to codify the naming. Document it.
Manual conversion
// Don't do this
const snakeToCamel = str => str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
That regex works for the happy path but breaks on:
- Strings starting with underscores:
_private_field - Numbers in strings:
field_2_value - Consecutive underscores:
some__field - Uppercase letters already present:
HTTP_STATUS
Rolling your own case converter for production use is a maintenance trap.
Applying conversion inconsistently
Converting only top-level keys in a nested object is worse than not converting at all — it creates a mixed-case structure that's harder to reason about than a consistently unconventional one.
// You converted the top level, but forgot the nested objects
{
"userId": 42,
"profile": {
"first_name": "Priya", // Still snake_case!
"last_name": "Sharma"
}
}
Ignoring edge cases in identifiers
Real data has edge cases. Acronyms like URL, HTTP, ID, and API are handled differently by different tools:
// camelCase: how do you write "user ID"?
userId // common
userID // also common
userid // wrong
// PascalCase
UserId // common
UserID // also common — Google style guides differ from Microsoft's here
Agreeing on acronym treatment within your team matters. Linters and formatter configs can enforce it.
Practical Code Examples
JavaScript: Converting between cases
// A robust camelCase → snake_case converter
function camelToSnake(str) {
return str
.replace(/([A-Z])/g, '_$1')
.toLowerCase()
.replace(/^_/, ''); // remove leading underscore if str started with uppercase
}
camelToSnake('firstName'); // → 'first_name'
camelToSnake('userID'); // → 'user_i_d' ← acronym problem!
camelToSnake('createdAt'); // → 'created_at'
// For full recursive object key conversion
function keysToCamel(obj) {
if (Array.isArray(obj)) {
return obj.map(keysToCamel);
} else if (obj !== null && typeof obj === 'object') {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [
key.replace(/_([a-z])/g, (_, c) => c.toUpperCase()),
keysToCamel(value)
])
);
}
return obj;
}
// Usage with API response
const apiResponse = {
user_id: 42,
first_name: "Priya",
address: {
zip_code: "400001",
city_name: "Mumbai"
}
};
console.log(keysToCamel(apiResponse));
// { userId: 42, firstName: 'Priya', address: { zipCode: '400001', cityName: 'Mumbai' } }
Python: Clean case conversion with the humps library
import humps
# snake_case → camelCase
humps.camelize('first_name') # 'firstName'
humps.camelize({'first_name': 'Priya'}) # {'firstName': 'Priya'}
# camelCase → snake_case
humps.decamelize('firstName') # 'first_name'
# Great for FastAPI response models
from pydantic import BaseModel
from humps import camelize
def to_camel(string: str) -> str:
return camelize(string)
class UserResponse(BaseModel):
user_id: int
first_name: str
created_at: str
class Config:
alias_generator = to_camel
populate_by_name = True
# Now your API returns camelCase JSON automatically
# while your Python code stays snake_case internally
Python: Converting to slug/kebab-case for URLs
import re
def to_kebab(text: str) -> str:
# Lowercase
text = text.lower()
# Replace spaces and underscores with hyphens
text = re.sub(r'[\s_]+', '-', text)
# Remove anything that's not alphanumeric or hyphen
text = re.sub(r'[^a-z0-9-]', '', text)
# Collapse multiple hyphens
text = re.sub(r'-+', '-', text)
return text.strip('-')
to_kebab("Understanding Case Conversion!") # → 'understanding-case-conversion'
to_kebab("C++ is (still) alive") # → 'c-is-still-alive'
to_kebab(" weird spacing ") # → 'weird-spacing'
TypeScript: Type-safe case conversion utilities
// Leveraging template literal types for compile-time checks
type CamelToSnake<T extends string> =
T extends `${infer Head}${infer Tail}`
? Head extends Uppercase<Head>
? `_${Lowercase<Head>}${CamelToSnake<Tail>}`
: `${Head}${CamelToSnake<Tail>}`
: T;
type SnakeToCamel<T extends string> =
T extends `${infer Head}_${infer Tail}`
? `${Head}${Capitalize<SnakeToCamel<Tail>>}`
: T;
// Example
type DBField = 'first_name' | 'created_at' | 'user_id';
type JSField = SnakeToCamel<DBField>;
// → 'firstName' | 'createdAt' | 'userId'
Best Practices for Handling Case Conversion in Real Projects
1. Decide at the boundary, not in the middle
Case conversion should happen at the edges of your system — at the API layer, the ORM layer, the URL router — not scattered throughout business logic. This keeps your internal code consistent and lets you change the conversion strategy without touching core logic.
Frontend (camelCase) ↔ [API boundary converts] ↔ Backend (snake_case) ↔ [ORM converts] ↔ DB (snake_case)
2. Use middleware for API conversion
In Express.js, a simple middleware handles the entire camelCase ↔ snake_case bridge:
const { camelizeKeys, decamelizeKeys } = require('humps');
// Convert incoming request bodies from camelCase to snake_case
app.use((req, res, next) => {
if (req.body) req.body = decamelizeKeys(req.body);
next();
});
// Convert outgoing responses from snake_case to camelCase
app.use((req, res, next) => {
const originalJson = res.json.bind(res);
res.json = (data) => originalJson(camelizeKeys(data));
next();
});
Now your backend never thinks about case conversion again.
3. Enforce conventions with linters
Don't rely on code review to catch case inconsistencies — automate it.
// .eslintrc — enforce camelCase in JavaScript
{
"rules": {
"camelcase": ["error", { "properties": "always" }]
}
}
# pyproject.toml — Ruff enforces PEP 8 naming
[tool.ruff]
select = ["N"] # naming conventions
4. Generate types from your API schema
If you're using OpenAPI, generate TypeScript types from it. The generator handles casing based on your configuration, and you never hand-write the mapping.
npx openapi-typescript api-schema.yaml -o src/types/api.ts
5. Treat UI labels separately from field names
Don't derive display labels by converting field names. snake_case to Title Case is lossy — user_id becomes "User Id", not "User ID". Maintain a separate display name map or use i18n keys.
const FIELD_LABELS = {
userId: 'User ID',
firstName: 'First Name',
createdAt: 'Date Created',
};
6. Document your conventions — even briefly
A single paragraph in your CONTRIBUTING.md saves hours of confusion:
API responses use
camelCase. Database columns usesnake_case. Environment variables useSCREAMING_SNAKE_CASE. URL slugs usekebab-case. Constants useSCREAMING_SNAKE_CASE. Component names usePascalCase.
That's it. Nobody has to guess.
A Note on Tools
The ecosystem has good options for case conversion across every major language:
-
JavaScript/TypeScript:
humps,lodash(_.camelCase, _.snakeCase, _.kebabCase),change-case -
Python:
humps(pyhumps),inflection,python-slugifyfor URL slugs -
Ruby: ActiveSupport includes
.camelize,.underscore,.dasherize -
Go:
strcaselibrary -
Rust:
convert_casecrate
Most mature frameworks also have built-in support — Django REST Framework can serialize with camelCase via a serializer setting; Next.js has route segment conventions baked in.
You don't need to pick any specific one — evaluate what fits your stack, check it handles edge cases (acronyms, numbers, empty strings, already-converted input), and use it consistently.
Wrapping Up
Case conversion feels like a small thing until it isn't. The inconsistencies compound: a bug here, a confusing API there, a URL that drops your SEO ranking. Getting it right is mostly about making conscious decisions early and then automating them so you never think about it again.
The short version:
- Know the conventions for each layer of your stack
- Convert at the boundary, not in the middle
- Automate with middleware, linters, and code generation
- Document your decisions so they survive the next developer
Consistent casing is one of those low-effort, high-leverage habits that quietly makes everything a little more professional — and a lot more maintainable.
Have a casing war story or a convention your team uses? Drop it in the comments — always curious how other teams solve this.
Top comments (0)