InTab

Blog

Naming conventions for APIs, JSON, databases and files

By Uxair Ijaz · 2026-11-06 · 8 min read

Naming is the cheapest refactoring and the most expensive mistake. A renamed column costs a migration; a renamed API field costs every client; an inconsistent scheme costs a little confusion every single day, forever. This guide sets out one coherent scheme across APIs, JSON, databases and code — with the reasoning, so you can deviate deliberately instead of accidentally.

URL paths: plural nouns, kebab-case, no verbs

GET   /orders                 ✓
GET   /order                  ✗ singular
GET   /getOrders              ✗ verb
GET   /Orders/42              ✗ capitals
GET   /order_items/9          ✓ (or /order-items/9 — pick one, see below)
GET   /orders/42/items        ✓ nested for ownership, two levels max

The rules professionals keep:

  • Plural collections, singular-with-ID resources: /orders/42. Reads like English: "order 42 from the orders".
  • Methods are the verbs. POST /orders/42/cancellation is legitimate for non-CRUD actions; POST /cancelOrder?id=42 is RPC in a trench coat.
  • kebab-case (hyphenated) paths dominate modern APIs — invoice-templates, not invoiceTemplates or invoice_templates. Hostnames are case-insensitive; paths are not, so all-lowercase paths remove a whole class of 404s.
  • Hierarchy is ownership, not composition: /customers/7/orders ("customer 7's orders"), then flatten below that level.
  • Versions in the path: /v1/orders. Visible in logs, trivially routed, honest about being a deployment boundary.

One genuine fork: hyphens or underscores in paths? Both appear in serious APIs (GitHub uses hyphens, AWS uses underscores in places). Hyphens edge it: they never need %-encoding discussion, they read better, and they cannot collide with query-parameter conventions. Choose per API, never mix.

JSON fields: camelCase or snake_case?

The two live conventions:

{ "createdAt": "2026-11-06T09:30:00Z", "orderId": "42" }     // camelCase
{ "created_at": "2026-11-06T09:30:00Z", "order_id": "42" }   // snake_case

Who uses which: Google, Facebook and most JavaScript-first shops use camelCase; Stripe, Twitter/X, AWS and Python-first shops use snake_case.

The deciding facts:

  • JavaScript consumes JSON by default, and camelCase needs no mapping there (body.orderId just works). Snake_case JSON in a TypeScript codebase grows a conversion layer or a field-access style that quietly forks the codebase.
  • snake_case is easier to read when values are dense acronyms: tax_id_number vs taxIdNumber.
  • Whatever you choose, do not mix — and that includes acronyms: orderId, never orderID in camelCase; user_id, never userID in snake_case.

What professionals choose: camelCase for JSON APIs whose main clients are browsers and JavaScript services; snake_case when the API is Python/Data-flavoured or mirrors an existing snake_case system. Either is fine on day one and expensive on day 300 — decide before the first field ships.

Two more field rules that prevent real bugs:

  • Booleans read as predicates: "is_active", "has_banner", "paid". Avoid bare nouns ("active": true invites "active what?").
  • No verbs in field names: "is_cancelled": true, not "cancelled_at_time_of_writing": ... — and timestamps end in _at (created_at, cancelled_at), durations end in _seconds/_ms.

Query parameters: the reserved words

Standardise these from day one, because every endpoint will grow them eventually:

Parameter Purpose Example
limit / cursor (or page/offset) pagination ?limit=20&cursor=abc
sort ordering, - for descending ?sort=-created_at
q free-text search ?q=blue
status, since, until filters ?status=placed&since=2026-11-01

Match their case to your JSON convention (created_at sorts a snake_case API; createdAt a camelCase one) — clients copy field names into sort/filter params constantly, and a mismatch there is a guaranteed support ticket.

Databases: snake_case all the way

Postgres, MySQL and most ORMs converge on the same conventions, and they differ from your API's on purpose — the API layer is the translator:

  • Tables plural, snake_case: orders, order_items, users.
  • Columns snake_case: created_at, total_cents, status.
  • Primary key id; foreign keys <singular>_id: customer_id.
  • Timestamps: created_at and updated_at on every table, timestamptz, no exceptions.
  • Constraint names follow what the error message will show: pk_orders, fk_orders_customer_id, uq_users_email, idx_orders_created_at. When production pages you at 3 a.m., uq_users_email violated is the diagnosis, not constraint_7 violated.
  • Booleans: is_/has_ prefixes (is_cancelled), no bare adjectives.

Why translate at the edge? Because each world's defaults win locally: the database community reads snake_case best, the JavaScript community reads camelCase best, and the mapping lives in exactly one layer (the ORM/serialiser) instead of leaking into both.

Code and files: follow the ecosystem, not your taste

Every ecosystem already voted; the professional move is to comply:

Ecosystem Files Symbols
Angular, Angular-style TS kebab-case (invoice-generator.ts) camelCase classes (InvoiceGenerator)
React/Next PascalCase components (InvoiceForm.tsx) or kebab, per repo PascalCase components, camelCase functions
Python snake_case (invoice_generator.py) snake_case functions, PascalCase classes
Go snake_case MixedCaps, no underscores
Java PascalCase files matching the class camelCase methods
CSS/utility classes kebab-case (invoice-card) —

Mixed-ecosystem repos pick per directory and write the rule in the README — one CONTRIBUTING.md paragraph prevents a hundred pull-request comments.

HTTP headers and other strangers

  • Headers: Content-Type, X-Request-Id — hyphenated, case-insensitive by spec (send them capitalised anyway; proxies and log parsers read case).
  • Environment variables: SCREAMING_SNAKE_CASE (DATABASE_URL, JWT_SECRET) — universal, don't innovate.
  • Query string encoding: names in your chosen convention; values URL-encoded (why %20 appears — and what encodeURI vs encodeURIComponent actually do).
  • Git branches: type/ticket-description (fix/231-invoice-total), kebab-case — sortable, greppable.

The one-page scheme

Copy this into your team wiki and you have adopted the majority position of the industry:

Layer Convention Example
URL paths plural nouns, kebab-case, /v1/ prefix /v1/invoice-templates/9
JSON fields camelCase (or snake_case — one only) createdAt
Query params matches JSON convention; reserved: limit, cursor, sort, q ?sort=-createdAt
Database snake_case, plural tables, _id FKs, created_at everywhere orders.created_at
Constraints pk_ / fk_ / uq_ / idx_ + table + column uq_users_email
Env vars SCREAMING_SNAKE_CASE JWT_SECRET
Headers Hyphenated, capitalised X-Request-Id

The rule underneath all the rules

Every convention here serves one of two masters: the reader (kebab-case paths, created_at, uq_users_email) and the machine (stable error types, reserved query words, one case per layer). When you face a case this guide did not cover, ask which master the name serves — and if the answer is "neither", the name is fine; stop deliberating and ship.

Need to check what a field is called everywhere else before you pick? The case converter flips a name between camelCase, snake_case, kebab-case and friends, and the JSON formatter shows your real-world payloads in the convention you actually shipped — which is sometimes not the one in this table.

Tools in this article

  • Case Converter: camelCase, snake_case, kebab-case, PascalCase and more.
  • JSON Formatter: Beautify, minify, validate and sort JSON. Handles escaped/stringified JSON.